#!/bin/bash
# **********************************************************
# * Author : liangliangSu
# * Email : sll917@hotmail.com
# * Create time : 2022-10-22 11:05
# * Filename : grep.sh
# **********************************************************
: <<!
一、grep基本介绍
作用:文本搜索工具，根据用户指定的“模式（过滤条件)”对目标文本逐行进行匹配检查，打印匹配到的行
grep格式
grep [option] pattern filename
grep的option选项说明
--color   显示颜色的参数，即搜索出来的关键字符带有颜色。使用"grep --color 关键字符" 或者 "grep --color=auto 关键字符"。一般配置别名alias grep="grep --color"

-a   不要忽略二进制数据。使用"grep -a 关键字符"

-A   显示符合关键字符的行, 以及其后面的n行。使用 "grep -An 关键字符" 或者 "grep -A n 关键字符"

-b   显示符合关键字符的行, 以及其前后的各n行。使用"grep -bn 关键字符", 注意这个不能使用"grep -b n 关键字符"! 跟-C参数差不多，但是会多打印出行号！！！

-B   显示符合关键字符的行, 以及其前面的n行。 使用 "grep -An 关键字符" 或者 "grep -A n 关键字符"

-c   只输出匹配行的计数。即只显示出匹配关键字符的那行的行数，不显示内容！使用"grep -c 关键字符"

-C   显示符合关键字符的行, 以及其前后的各n行。使用 "grep -Cn 关键字符" 或者 "grep -C n 关键字符"。跟-b参数差不多，-b参数会多打印行号出来！

-d   当指定要查找的是目录而非文件时，必须使用这项参数，否则grep命令将回报信息并停止动作。-d后面跟的是进行的动作，一般是"grep -d read"或"grep -d recure"，后面会有小示例说明。

-e   指定字符串作为查找文件内容的关键字符。使用"grep -e 关键字符"。grep -e "正则表达式" 文件

-E   将关键字符为延伸的普通表示法来使用，意味着使用能使用扩展正则表达式！！通常用于满足多个条件中的其中一个条件进行搜索。使用grep -E "条件1|条件2|条件2"

-f   显示两个文件中相同的行。使用 "grep -f filename1 filename2 " 或者 "grep --file filename1 filename2",相等于"cat filename1 filename2|sort|uniq -d"，即取交集!

-F   将关键字符视为固定字符串的列表。使用"grep -F 关键字 filename1" 或者 "grep -F 关键字符 filename1 filename2 filenamen", 会显示出来关键字所在的文件的列表。

-G   将关键字符视为普通的表示法来使用。

-h   对多文件搜索关键字符时不显示文件名，只显示关键字符。使用"grep -h 关键字符 filename1 filename2 filenamen", 不显示文件名，只显示关键字符。

-H   对多文件搜索关键字符时显示文件名和关键字符，跟-h参数相反。 使用"grep -H 关键字符 filename1 filename2 filenamen", 显示"文件名:关键字符"。

-i   忽略关键字符的大小写。(跟-y参数相同)。使用"grep -i"

-l   对多文件搜索关键字符时只显示文件名。使用"grep -l 关键字符 filename1 filename2 filenamen"，只显示文件名，不显示关键字符。

-L   对多文件搜索关键字符时，只显示不匹配关键字符的文件名。使用"grep -L 关键字符 filename1 filename2 filenamen"，只显示不匹配关键字符的文件。

-n   显示匹配关键字符的行号和行内容。使用"grep -n 关键字符 filename"

-q   不显示任何信息。用于if逻辑判断,安静模式，不打印任何标准输出。如果有匹配的内容则立即返回状态值0。

-R/-r   此参数的效果和指定"-d recurse"参数相同。

-s   当搜索关键字符，匹配的文件不存在时不显示错误信息。即不显示不存在或无匹配文本的错误信息。 使用"grep -s 关键字符 filename"

-v   反转或过滤搜索，即过滤出来那些不匹配关键字符的行。使用"grep -v 关键字符"

-w   精准搜索关键字符，即只显示完全匹配关键字符的行,不显示那些包含关键字符的行。使用"grep -w 关键字符", 或者使用grep "\<关键字符\>"，或者使用grep "\b关键字符\b"

-x   只显示整行都是关键字符的行。使用"grep -x 关键字符"。

-y   忽略关键字符的大小写。(跟-i参数相同)。"grep -y"。

-o   只输出文件中匹配到的部分, 不会打印多余的内容。

-P   使用perl的正则表达式语法，因为perl的正则更加多元化，能实现更加复杂的场景。典型用法是匹配指定字符串之间的字符。（-e或-E是匹配扩展正则表达式，-P是匹配perl正则表达式）
模式:由正则表达式的元字符及文本字符所编写出的过滤条件﹔
grep命令是Linux系统中最重要的命令之一，功能是从文本文件或管道数据流中筛选匹配的行和数据，如果再配合正则表达式，功能十分强大，是Linux运维人员必备的命令
grep命令里的匹配模式就是你想要找的东西，可以是普通的文字符号，也可以是正则表达式

二、grep正则表达式元字符集（基本集）

^  匹配行的开始 如：'^grep'匹配所有以grep开头的行。

$  匹配行的结束 如：'grep$'匹配所有以grep结尾的行。

.  匹配一个非换行符的字符 如：'gr.p'匹配gr后接一个任意字符，然后是p。

*  匹配零个或多个先前字符 如：'*grep'匹配所有一个或多个空格后紧跟grep的行。 .*一起用代表任意字符。

[]  匹配一个指定范围内的字符，如'[Gg]rep'匹配Grep和grep。即[mn]表示匹配m或者n关键字符，相当于grep -E "m|n"。注意[]里面不要放太多关键字符，容易混乱！只要放[]里面的都是要匹配的关键字符！

[^]  匹配一个不在指定范围内的字符，如：'[^A-FH-Z]rep'匹配不包含A-F和H-Z的一个字母，但是包含rep并且rep不在开头（即*req）的行。

\(..\)  标记匹配字符，如'\(love\)'，love被标记为1。

\《  匹配单词的开始，如:'\
" '
\》 匹配单词的结束，如'str\>'匹配包含以str结尾的单词的行。通常使用"\<关键字符\>"作为精准匹配，相当于grep -w

x\{m\}  重复字符x，m次，如：'0\{5\}'匹配包含5个o的行。

x\{m,\}  重复字符x,至少m次，如：'o\{5,\}'匹配至少有5个o的行。

x\{m,n\}  重复字符x，至少m次，不多于n次，如：'o\{5,10\}'匹配5--10个o的行。

\w  匹配文字和数字字符，也就是[A-Za-z0-9]，如：'G\w*p'匹配以G后跟零个或多个文字或数字字符，然后是p。

\W  \w的反置形式，匹配一个或多个非单词字符，如点号句号等。

\b 单词锁定符，如: \byang\b 表示只匹配yang。相当于grep -w "yang" 或者 grep "\<yang\>"

+  匹配一个或多个先前的字符。如：'[a-z]+able'，匹配一个或多个小写字母后跟able的串，如loveable,enable,disable等。注意：先前字符必须使用()或[]括起来，并且使用grep -E。

?  匹配零个或一个先前的字符。如：'(gr)?p'匹配gr后跟一个或没有字符，然后是p的行。注意：先前字符必须使用()或[]括起来，并且使用grep -E。

a|b|c  匹配a或b或c。如grep -E "a|b|c", 匹配a或b或c中的任意一个都可以。grep -v "a\|b\|c" 或 egrep -v "a|b|c" 过滤掉a或b或c中的任意一个进行搜索。

()  分组符号，如：love(able|rs)ov+匹配loveable或lovers，匹配一个或多个ov。
!


echo "(1-1)-------------------------------------------------------------"
#示例：
grep -i root /etc/passwd                #忽略大小写匹配包含root的行
grep -w ftp /etc/passwd                 #精确匹配ftp单词
grep -w hello /etc/passwd               # 精确匹配hello单词;自己添加包含hello的行到文件中
grep -wo ftp /etc/passwd                # 打印匹配到的关键字ftp
grep -n root /etc/passwd                #打印匹配到root关键字的行好
grep -ni root /etc/passwd               # 忽略大小写匹配统计包含关键字root的行
grep -nic root /etc/passwd              # 忽略大小写匹配统计包含关键字root的行数
grep -i ^root /etc/passwd               # 忽略大小写匹配以root开头的行
grep bash$ /etc/passwd                  # 匹配以bash结尾的行
grep -n ^$ /etc/passwd                  # 匹配空行并打印行号
echo "(1-2)-------------------------------------------------------------"
grep -A 5 mail /etc/passwd | head -3             #匹配包含mail关键字及其后5行
grep -B 5 mail /etc/passwd | head -3            #匹配包含mail关键字及其前5行
grep -C 5 mail /etc/passwd | head -3            #匹配包含mail关键字及其前后5行

echo "(1-3)-------------------------------------------------------------"
# Linux Grep 命令高级用法实例
grep "^[[:alnum:]]" /etc/passwd	| head -5	    #搜索字母数字字符
grep "^[a-zA-Z0-9]]" /etc/passwd | head -5
echo "(1-4)-------------------------------------------------------------"
grep "^[[:alpha:]]" /etc/passwd | head -5    #搜索字母字符
grep "^[a-zA-Z]]" /etc/passwd | head -5
echo "(1-5)-------------------------------------------------------------"
grep "^[[:digit:]]" /etc/passwd | head -5    #搜索数字字符
grep "^[0-9]]" /etc/passwd | head -5
echo "(1-6)-------------------------------------------------------------"
grep "^[[:blank:]]" /etc/passwd | head -5    #搜索空白字符
echo "(1-7)-------------------------------------------------------------"
grep "^[[:punct:]]" /etc/passwd | head -5    #搜索空白字符
echo "(1-8)-------------------------------------------------------------"
grep "^[[:space:]]" /etc/passwd | head -5    #搜索空白字符
echo "(1-9)-------------------------------------------------------------"
grep "^[[:xdigit:]]" /etc/passwd | head -5    #搜索空白字符

# grep与正则表达式
echo "(10)-------------------------------------------------------------"
#1, 字符类
# 字符类的搜索：如果我想要搜寻 test 或 taste 这两个单词时，可以发现到，其实她们有共通的 't?st' 存在～这个时候，我可以这样来搜寻：
grep -n 't[ae]st' regular_grep.txt
# 其实 [] 里面不论有几个字节，他都谨代表某『一个』字节， 所以，上面的例子说明了，我需要的字串是『tast』或『test』两个字串而已！
 
# 字符类的反向选择 [^] ：如果想要搜索到有 oo 的行，但不想要 oo 前面有 g，如下
echo "(11)-------------------------------------------------------------"
grep -n '[^g]oo' regular_grep.txt
# 第 2,3 行没有疑问，因为 foo 与 Foo 均可被接受！
 
echo "(12)-------------------------------------------------------------"
grep -n '[^a-z]oo' regular_grep.txt
# 也就是说，当我们在一组集合字节中，如果该字节组是连续的，例如大写英文/小写英文/数字等等， 就可以使用[a-z],[A-Z],[0-9]等方式来书写，那么如果我们的要求字串是数字与英文呢？ 呵呵！就将他全部写在一起，变成：[a-zA-Z0-9]。
 
# 我们要取得有数字的那一行，就这样：
echo "(13)-------------------------------------------------------------"
grep -n '[0-9]' regular_grep.txt
# 行首与行尾字节 ^ $
# 行首字符：如果我想要让 the 只在行首列出呢？ 这个时候就得要使用定位字节了！我们可以这样做：
echo "(14)-------------------------------------------------------------"
grep -n '^the' regular_grep.txt

# 此时，就只剩下第 12 行，因为只有第 12 行的行首是 the 开头啊～此外， 如果我想要开头是小写字节的那一行就列出呢？可以这样：
echo "(15)-------------------------------------------------------------"
grep -n '^[a-z]' regular_grep.txt

# 如果我不想要开头是英文字母，则可以是这样：
echo "(16)-------------------------------------------------------------"
grep -n '^[^a-zA-Z]' regular_grep.txt
# ^ 符号，在字符类符号(括号[])之内与之外是不同的！ 在 [] 内代表『反向选择』，在 [] 之外则代表定位在行首的意义！
# 那如果我想要找出来，行尾结束为小数点 (.) 的那一行：
echo "(17)-------------------------------------------------------------"
grep -n '\.$' regular_grep.txt
# 特别注意到，因为小数点具有其他意义，所以必须要使用转义字符()来加以解除其特殊意义！
# 找出空白行：
echo "(18)-------------------------------------------------------------"
grep -n '^$' regular_grep.txt
# 因为只有行首跟行尾 (^$)，所以，这样就可以找出空白行啦！
 
# 任意一个字节 . 与重复字节 *
# 这两个符号在正则表达式的意义如下：
# . (小数点)：代表『一定有一个任意字节』的意思；* (星号)：代表『重复前一个字符， 0 到无穷多次』的意思，为组合形态
# 假设我需要找出 g??d 的字串，亦即共有四个字节， 起头是 g 而结束是 d ，我可以这样做：
echo "(19)-------------------------------------------------------------"
grep -n 'g..d' regular_grep.txt
# 因为强调 g 与 d 之间一定要存在两个字节，因此，第 13 行的 god 与第 14 行的 gd 就不会被列出来啦！
# 如果我想要列出有 oo, ooo, oooo 等等的数据， 也就是说，至少要有两个(含) o 以上，该如何是好

# 因为 * 代表的是『重复 0 个或多个前面的 RE 字符』的意义， 因此，『o』代表的是：『拥有空字节或一个 o 以上的字节』，因此，『 grep -n 'o' regular_grep.txt 』将会把所有的数据都列印出来终端上！
# 
# 当我们需要『至少两个 o 以上的字串』时，就需要 ooo* ，亦即是：
echo "(20)-------------------------------------------------------------"
grep -n 'ooo*' regular_grep.txt
# 如果我想要字串开头与结尾都是 g，但是两个 g 之间仅能存在至少一个 o ，亦即是 gog, goog, gooog…. 等等，那该如何？
echo "(21)-------------------------------------------------------------"
grep -n 'goo*g' regular_grep.txt
# 如果我想要找出 g 开头与 g 结尾的行，当中的字符可有可无
echo "(22)-------------------------------------------------------------"
grep -n 'g.*g' regular_grep.txt
# 因为是代表 g 开头与 g 结尾，中间任意字节均可接受，所以，第 1, 14, 20 行是可接受的喔！ 这个 .* 的 RE 表示任意字符是很常见的.
# 如果我想要找出『任意数字』的行？因为仅有数字，所以就成为：
echo "(23)-------------------------------------------------------------"
grep -n '[0-9][0-9]*' regular_grep.txt

# 限定连续 RE 字符范围 {}
# 我们可以利用 . 与 RE 字符及 * 来配置 0 个到无限多个重复字节， 那如果我想要限制一个范围区间内的重复字节数呢？
# 举例来说，我想要找出两个到五个 o 的连续字串，该如何作？这时候就得要使用到限定范围的字符 {} 了。 但因为 { 与 } 的符号在 shell 是有特殊意义的，因此， 我们必须要使用字符   \ 来让他失去特殊意义才行。 至於 {} 的语法是这样的，假设我要找到两个 o 的字串，可以是：
echo "(24)-------------------------------------------------------------"
grep -n 'o\{2\}' regular_grep.txt
# 假设我们要找出 g 后面接 2 到 5 个 o ，然后再接一个 g 的字串，他会是这样：
grep -n 'go\{2,5\}g' regular_grep.txt
# 如果我想要的是 2 个 o 以上的 goooo….g 呢？除了可以是 gooo*g ，也可以是：
grep -n 'go\{2,\}g' regular_grep.txt

# 扩展grep(grep -E 或者 egrep)：
# 使用扩展grep的主要好处是增加了额外的正则表达式元字符集。
# 打印所有包含NW或EA的行。如果不是使用egrep，而是grep，将不会有结果查出。
echo "(25)-------------------------------------------------------------"
egrep 'NW|EA' regular_grep.txt     
# 对于标准grep，如果在扩展元字符前面加\，grep会自动启用扩展选项-E。
echo "(26)-------------------------------------------------------------"
grep 'NW\|EA' regular_grep.txt
# 搜索所有包含一个或多个3的行。
echo "(27)-------------------------------------------------------------"
egrep '3+' regular_grep.txt
grep -E '3+' regular_grep.txt
grep '3\+' regular_grep.txt        

# 搜索所有包含0个或1个小数点字符的行。
echo "(28)-------------------------------------------------------------"
egrep '2\.?[0-9]' regular_grep.txt 
grep -E '2\.?[0-9]' regular_grep.txt
grep '2\.\?[0-9]' regular_grep.txt 
# #首先含有2字符，其后紧跟着0个或1个点，后面再是0和9之间的数字。

# 搜索一个或者多个连续的no的行。
echo "(29)-------------------------------------------------------------"
egrep '(no)+' regular_grep.txt
grep -E '(no)+' regular_grep.txt
grep '\(no\)\+' regular_grep.txt   
#3个命令返回相同结果，

# 不使用正则表达式
# fgrep 查询速度比grep命令快，但是不够灵活：它只能找固定的文本，而不是规则表达式。
# 如果你想在一个文件或者输出中找到包含星号字符的行
echo "(30)-------------------------------------------------------------"
fgrep  '*' /etc/profile
grep -F '*' /etc/profile
# 找出文件中存在的空行，并且将空行排除，打印除空行外的内容
echo "(31)-------------------------------------------------------------"
grep -n -v "^$" regular_grep.txt 
